using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
using Random = System.Random;

namespace ShaderSnake
{
	public class Snake : MonoBehaviour
	{
		private Random _random = new Random();
		
		private Texture2D _screen;
		[SerializeField] private RawImage image;
		
		public List<Vector2Int> snake = new();

		private float _lastTickInterval = -100f;
		public float tickInterval = 0.1f;

		private Vector2Int _lastPos;

		private Vector2Int[] _dirs =
		{
			new(0, 1),
			new(0, -1),
			new(-1, 0),
			new(1, 0),
		};

		private int dirIdx = 0;
		
		[SerializeField] private Vector2Int foodPos;
		private bool _ateFood = false;

		private bool _dead;

		public void InitGame()
		{
			snake = new List<Vector2Int>();
			
			for (int i = 0; i < 5; ++i)
				snake.Add(new (0, 0));
		}

		public void TickSnake()
		{
			var head = snake[^1];
			var next = head + _dirs[dirIdx];
			
			if (snake.Contains(next) && next != snake[0])
			{
				_dead = true;
				StartCoroutine(DeathCoroutine());
				return;
			}
			
			snake.Add(next);

			if (snake.Contains(foodPos))
			{
				Debug.Log("Ate food");
				_ateFood = true;
			}
		}

		private void Start()
		{
			_screen = new Texture2D(32, 32);
			_screen.filterMode = FilterMode.Point;
			ClearScreen();
			image.texture = _screen;
			
			InitGame();
			GenerateFood();
		}

		private bool CheckDir(int willingDir)
		{
			return snake.Count <= 1 || (_dirs[willingDir] + _dirs[dirIdx]).magnitude != 0;
		}

		private void Update()
		{
			if (_dead)
				return;
			
			if (Input.GetKeyDown(KeyCode.UpArrow) && CheckDir(0))
			{
				dirIdx = 0;
			}
			else if (Input.GetKeyDown(KeyCode.DownArrow) && CheckDir(1))
			{
				dirIdx = 1;
			}
			else if (Input.GetKeyDown(KeyCode.LeftArrow) && CheckDir(2))
			{
				dirIdx = 2;
			}
			else if (Input.GetKeyDown(KeyCode.RightArrow) && CheckDir(3))
			{
				dirIdx = 3;
			}
				
			if (Time.time - _lastTickInterval > tickInterval)
			{
				_lastTickInterval = Time.time;
				TickSnake();
				UpdateScreen();
			}
		}

		private void GenerateFood()
		{
			Debug.Log("Generating food");
			do
			{
				foodPos = new(_random.Next(2, _screen.width - 2), _random.Next(2, _screen.height - 2));
				foodPos.x -= _screen.width / 2;
				foodPos.y -= _screen.height / 2;
			} while (snake.Contains(foodPos));
			
			Point(foodPos.x, foodPos.y, Color.red);		
		}
		private void ClearScreen()
		{
			for (int y = 0; y < _screen.height; y++)
			{
				for (int x = 0; x < _screen.width; x++)
				{
					_screen.SetPixel(x, y, new Color(0, 0, 0, 1));
				}
			}
			
			_screen.Apply();
		}

		private void Point(int x, int y, Color color)
		{
			var scrW = _screen.width;
			var scrH = _screen.height;

			var clearPos = snake[0];
			_screen.SetPixel(x + scrW / 2, y + scrH / 2, color);
		}

		private void UpdateScreen()
		{
			var scrW = _screen.width;
			var scrH = _screen.height;

			if (!_ateFood)
			{
				var clearPos = snake[0];
				_screen.SetPixel(clearPos.x + scrW / 2, clearPos.y + scrH / 2, new Color(0, 0, 0, 1));

				snake.RemoveAt(0);
			}
			else
			{
				GenerateFood();
				
				_ateFood = false;
			}

			foreach (var pos in snake)
			{
				var offsetPos = new Vector2Int(pos.x + scrW / 2, pos.y + scrH / 2);
				_screen.SetPixel((int)offsetPos.x, (int)offsetPos.y, Color.white);
			}
			
			_screen.Apply();
		}

		private IEnumerator DeathCoroutine()
		{
			yield return new WaitForSeconds(0.5f);
			
			var scrW = _screen.width;
			var scrH = _screen.height;

			foreach (var pos in snake)
			{
				var offsetPos = new Vector2Int(pos.x + scrW / 2, pos.y + scrH / 2);
				_screen.SetPixel((int)offsetPos.x, (int)offsetPos.y, Color.black);
				
				_screen.Apply();

				yield return new WaitForSeconds(0.05f);
			}

			yield return new WaitForSeconds(1f);

			_dead = false;
			InitGame();
			ClearScreen();
			GenerateFood();
		}
	}
}